package com.example.protocol.mqtt.handler;

import com.example.protocol.entity.BatteryData;
import com.example.protocol.entity.CabinetData;
import com.example.protocol.entity.pointtable.BatteryPointTable;
import com.example.protocol.entity.pointtable.CabinetPointTable;
import com.example.protocol.entity.pointtable.PointInfo;
import com.example.protocol.service.*;
import com.example.protocol.mqtt.CallBackHandler;
import com.example.protocol.util.ByteTransfer;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

@Slf4j
public class CabinetStateHandler implements CallBackHandler {
    String topic = "cabinet/state";

    CabinetPointTable cabinetPointTable;

    BatteryPointTable batteryPointTable;

    ICabinetDataService cabinetDataService;
    IBatteryDataService batteryDataService;

    // 由于需要在新线程里面使用被@Service注解修饰的类，但是spring策略是非线程安全就不让你注入，所以只能在构造的时候传进来
    // 详见 https://blog.csdn.net/yuyeqianhen/article/details/90038123
    public CabinetStateHandler(ICabinetDataService cabinetDataService, IBatteryDataService batteryDataService) {
        this.cabinetDataService = cabinetDataService;
        this.batteryDataService = batteryDataService;
    }

    @Override
    public String getHandleableTopic() {
        return this.topic;
    }

    @Override
    public void handleTopic(MqttMessage msg) {
        try {
            log.info("{} handle {}", this.getClass().getName(), this.topic);
            byte[] rawBytes = msg.getPayload();

            // 处理电池柜
            int offset = 0;
            CabinetData cabinetData = new CabinetData();
            for (PointInfo p : CabinetPointTable.pointTable) {
                setValueToEntityByPointData(rawBytes, offset, cabinetData, p);
                offset = offset + p.getLen();
            }
            log.info("{}", cabinetData);

            LocalDateTime readTime = LocalDateTime.now();
            cabinetData.setReadTime(readTime);
            cabinetDataService.save(cabinetData);


            // 处理电池
            // 需要计算电池的数量，电池数量 = (rawBytes.length - 电池柜点表数据总长) / 电池点表数据总长
            // 理论上此时的 offset 等于 电池柜点表数据总长
            // 那么计算一下电池柜的点表数据总长
            int batteryDataLength = 0;
            for (PointInfo p :
                    BatteryPointTable.pointTable) {
                batteryDataLength += p.getLen();
            }
            int batteryNum = (rawBytes.length - offset) / batteryDataLength;
            log.info("batteryNum: {}", batteryNum);
            for (int i = 0; i < batteryNum; i++) {
                log.info("--battery--");
                BatteryData batteryData = new BatteryData();
                for (PointInfo p : BatteryPointTable.pointTable) {
                    setValueToEntityByPointData(rawBytes, offset, batteryData, p);
                    offset = offset + p.getLen();
                }
                batteryData.setReadTime(readTime);
                batteryData.setBatteryOrder(i+1);  // 设定电池序号
                batteryData.setCabinetId(cabinetData.getCabinetId());  // 设定所在的电池柜ID
                batteryDataService.save(batteryData);
            }



        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object clone() {
        CabinetStateHandler cabinetStateHandler = null;
        try {
            cabinetStateHandler = (CabinetStateHandler) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return cabinetStateHandler;
    }

    private void setValueToEntityByPointData(byte[] rawBytes, int offset, Object entity, PointInfo p) {
        byte[] b = new byte[p.getLen()];
        System.arraycopy(rawBytes, offset, b, 0, p.getLen());
        switch (p.getType()) {
            case "string":
                String str = new String(b, StandardCharsets.UTF_8); // 处理字符串
                log.info("{}", str);
                setFieldValueByFieldName(p.getEntityFieldName(), entity, str.getClass(), str);
                break;
            case "unsigned short":
                Integer us = ByteTransfer.bytesToUnsignedShort(b[0], b[1]); // 处理16位无符号数
                log.info("{}", us);
                setFieldValueByFieldName(p.getEntityFieldName(), entity, us.getClass(), us);
                break;
            case "short":
                Integer s = (int) ByteTransfer.bytesToSignedShort(b[0], b[1]);  // 处理16位有符号数
                // 尽管数据库的类型是small integer，
                // 但是MyBatis Plus Generator生成的实体类的属性类型却是Integer，而不是short
                // 那就这样吧，short转int也不存在精度丢失的问题，所以上面会有一次强制转换
                log.info("{}", s);
                setFieldValueByFieldName(p.getEntityFieldName(), entity, s.getClass(), s);
                break;
            case "float":
                BigDecimal bigDecimal = BigDecimal.valueOf(ByteTransfer.bytesToFloat(b));
                log.info("{}", bigDecimal);
                setFieldValueByFieldName(p.getEntityFieldName(), entity, bigDecimal.getClass(), bigDecimal);
                break;
        }
    }

    /**
     * 根据属性名获取属性值
     * https://blog.csdn.net/qiesheng/article/details/79702163
     *
     * @param fieldName
     * @param object
     * @return
     */
    private String getFieldValueByFieldName(String fieldName, Object object) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            //设置对象的访问权限，保证对private的属性的访问
            field.setAccessible(true);
            return (String) field.get(object);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 根据属性名设置属性值
     * https://blog.csdn.net/qiesheng/article/details/79702163
     *
     * @param fieldName
     * @param object
     * @return
     */
    private <T> void setFieldValueByFieldName(String fieldName, Object object, Class<T> classType, Object value) {
        try {
            // 获取obj类的字节文件对象
            Class c = object.getClass();
            // 获取该类的成员变量
            Field f = c.getDeclaredField(fieldName);
            // 取消语言访问检查
            f.setAccessible(true);
            // 给变量赋值
            f.set(object, (T) value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }


}
